﻿/********************************************************************************
* Copyright 2010 Zane Thorn (zane.thorn@gmail.com)                              *
*                                                                               *
* NeturalMath is free software: you can redistribute it and/or modify           *
* it under the terms of the GNU Lesser General Public License as published by   *
* the Free Software Foundation, either version 3 of the License, or             *
* (at your option) any later version.                                           *
*                                                                               *
* NeturalMath is distributed in the hope that it will be useful,                *
* but WITHOUT ANY WARRANTY; without even the implied warranty of                *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
* GNU Lesser General Public License for more details.                           *
*                                                                               *
* You should have received a copy of the GNU Lesser General Public License      *
* along with NeturalMath.  If not, see <http://www.gnu.org/licenses/>.          *
********************************************************************************/

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Linq.Expressions;
using NeturalMath.Expressions;
using NeturalMath.Units;

namespace NeturalMath
{
    /// <summary>
    /// The base class for values used inside of NeturalMath
    /// </summary>
    public abstract class MathValue:DynamicObject,IComparable<MathValue>
    {
        #region Constructors

        /// <summary>
        /// Creates a new instance of MathValue
        /// </summary>
        /// <param name="type"></param>
        /// <param name="typeName"></param>
        /// <param name="value"></param>
        internal MathValue(ValueTypes type,string typeName,object value,MathRuntime runtime)
        {
            Type = type;
            Value = value;
            TypeName = typeName;
            Runtime = runtime;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Get the raw data for this object
        /// </summary>
        public object Value { get; private set; }

        /// <summary>
        /// Gets the type of data this object contains
        /// </summary>
        public ValueTypes Type { get; private set; }

        /// <summary>
        /// Gets the string name of this data type
        /// </summary>
        public string TypeName { get; private set; }

        public MathRuntime Runtime { get; private set; }

        #endregion

        #region Public Methods

        public virtual object GetValue()
        {
            return Value;
        }

        #endregion

        #region Method Overrides

        /// <summary>
        /// Overrides the dynamic TryBinaryOperation method for interoperability with C#
        /// </summary>
        /// <param name="binder"></param>
        /// <param name="arg"></param>
        /// <param name="result"></param>
        /// <returns></returns>
        public override bool TryBinaryOperation(BinaryOperationBinder binder, object arg, out object result)
        {
            var other = arg as MathValue;
            bool? bv = null;
            var native = ConvertToNativeValue(other);

            result = null;
            switch(binder.Operation)
            {
                case ExpressionType.Add:
                    result= Add(native);
                    break;
                case ExpressionType.Subtract:
                    result = Subtract(native);
                    break;
                case ExpressionType.Multiply:
                    result = Multiply(native);
                    break;
                case ExpressionType.Divide:
                    result = Divide(native);
                    break;
                case ExpressionType.Power:
                    result = Power(native);
                    break;
                case ExpressionType.Modulo:
                    result = Modulo(native);
                    break;
                case ExpressionType.Equal:
                    result = Runtime.NewBool(Equals(native));
                    break;
                case ExpressionType.NotEqual:
                    result = Runtime.NewBool(!Equals(native));
                    break;
                case ExpressionType.GreaterThan:
                    bv = IsGreaterThan(native);
                    result = (bv == null) ? Runtime.Undefined : Runtime.NewBool(bv.Value);
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    bv = IsLessThan(native);
                    result = (bv == null) ? Runtime.Undefined : Runtime.NewBool(!bv.Value);
                    break;
                case ExpressionType.LessThan:
                    bv = IsLessThan(native);
                    result = (bv == null) ? Runtime.Undefined : Runtime.NewBool(bv.Value);
                    break;
                case ExpressionType.LessThanOrEqual:
                    bv = IsGreaterThan(native);
                    result = (bv == null) ? Runtime.Undefined : Runtime.NewBool(!bv.Value);
                    break;
                case ExpressionType.And:
                    result = And(native);
                    break;
                case ExpressionType.Or:
                    result = Or(native);
                    break;
                case ExpressionType.ExclusiveOr:
                    result = Xor(native);
                    break;
                default:
                    throw new InvalidOperationException();
            }
            return true;
        }

        /// <summary>
        /// Overload of default Equals method
        /// </summary>
        /// <param name="obj"></param>
        /// <returns>True if objects are identical</returns>
        public override bool Equals(object obj)
        {
            if (obj == null)
                return false;

            if (System.Object.ReferenceEquals(this, obj))
                return true;

            var value = obj as MathValue;
            if (value == null)
                return false;

            if (value is VoidValue)
            {
                return (this is VoidValue);
            }

            var result = false;
            try
            {
                var other = ConvertToNativeValue(value);
                result = Value.Equals(other.Value);
            }
            catch
            {
                // should return false
            }

            return result;
        }

        /// <summary>
        /// Get the HashCode of the Object
        /// </summary>
        /// <returns>Returns HashCode for the nested value.</returns>
        public override int GetHashCode()
        {
            return Value.GetHashCode();
        }

        #endregion

        #region Helper Methods

        #region Math Methods

        /// <summary>
        /// Adds two values and returns the result
        /// </summary>
        /// <param name="arg">Value to add</param>
        /// <returns>The sum of the values added</returns>
        protected abstract MathValue Add(MathValue arg);

        /// <summary>
        /// Subtracts value and returns the result
        /// </summary>
        /// <param name="arg">Value to subtract</param>
        /// <returns>The difference of the values subtracted</returns>
        protected abstract MathValue Subtract(MathValue arg);

        /// <summary>
        /// Multiplies two values and returns the result
        /// </summary>
        /// <param name="arg">Value to multiply</param>
        /// <returns>The product of the values multiplied</returns>
        protected abstract MathValue Multiply(MathValue arg);

        /// <summary>
        /// Divides two values and returns the result
        /// </summary>
        /// <param name="arg">Value to divide by</param>
        /// <returns>The dividend of the values divided</returns>
        protected abstract MathValue Divide(MathValue arg);

        /// <summary>
        /// Divides two values and returns the remainder
        /// </summary>
        /// <param name="arg">Value to divide by</param>
        /// <returns>The remainder of the values divided</returns>
        protected abstract MathValue Modulo(MathValue arg);

        /// <summary>
        /// Raises the value to the power provided
        /// </summary>
        /// <param name="arg">Value to raise</param>
        /// <returns>The exponent of the values</returns>
        protected abstract MathValue Power(MathValue arg);

        protected virtual MathValue PlusPlus()
        {
            return this + Runtime.NewNumber(1.0);
        }

        protected virtual MathValue MinusMinus()
        {
            return this - Runtime.NewNumber(1.0);
        }
        
        #endregion

        #region Logical Methods

        /// <summary>
        /// Compares two values and returns the result of a AND operation
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected abstract MathValue And(MathValue arg);

        /// <summary>
        /// Compares two values and returns the result of an OR operation
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected abstract MathValue Or(MathValue arg);

        /// <summary>
        /// Compares two values and returns the result of an OR operation
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        protected abstract MathValue Xor(MathValue arg);

        #endregion

        #region Conversion Methods

        /// <summary>
        /// Returns the result of a TRUE operation
        /// </summary>
        /// <returns></returns>
        protected abstract bool IsTrue();

        /// <summary>
        /// Returns the result of an MINUS operation
        /// </summary>
        /// <returns></returns>
        protected abstract MathValue ToMinus();

        /// <summary>
        /// Flips the bits associated with this object
        /// </summary>
        /// <returns></returns>
        protected abstract MathValue Flip();

        /// <summary>
        /// Converts the current value to a string
        /// </summary>
        /// <returns>A string representation of the value</returns>
        protected internal abstract MathValue ConvertToString();

        /// <summary>
        /// Converts the current value to a number
        /// </summary>
        /// <returns>A numeric representation of the value</returns>
        protected internal abstract MathValue ConvertToNumber();

        /// <summary>
        /// Converts the current value to a bool
        /// </summary>
        /// <returns>A boolean representation of the value</returns>
        protected internal abstract MathValue ConvertToBoolean();

        /// <summary>
        /// Converts the current value to a range
        /// </summary>
        /// <returns></returns>
        protected internal abstract MathValue ConvertToRange();

        /// <summary>
        /// Converts the current value to a complex
        /// </summary>
        /// <returns></returns>
        protected internal abstract MathValue ConvertToComplex();


        /// <summary>
        /// Converts the current value to a unit based on the provided measure
        /// </summary>
        /// <param name="measure"></param>
        /// <returns></returns>
        protected internal abstract MathValue ConvertToUnit(UnitMeasure measure);

        protected internal MathValue ConvertToUnit()
        {
            return ConvertToUnit(UnitMeasure.Count);
        }

        /// <summary>
        /// Converts the value into the same value type as this object
        /// </summary>
        /// <returns>The provided value in the form of this type of data</returns>
        protected abstract MathValue ConvertToNativeValue(MathValue arg);

        /// <summary>
        /// Gets the string returned when ToString() is called
        /// </summary>
        /// <returns>A string representation of the data</returns>
        protected abstract string GetToString();

        /// <summary>
        /// Converts this value into an expression
        /// </summary>
        /// <returns>This value as an expression value</returns>
        

        /// <summary>
        /// Converts the current value to a set
        /// </summary>
        /// <returns></returns>
        protected internal MathValue ConvertToSet()
        {
            if (this is SetValue)
                return this;
            return Runtime.NewSet(new[] {new ConstantValueExpression(this,Runtime)});
        }

        /// <summary>
        /// Returns a string representing this value
        /// </summary>
        /// <returns>A string representation of the data</returns>
        public override sealed string ToString()
        {
            return GetToString();
        }

        #endregion

        #region Comparison Methods

        /// <summary>
        /// Determines if the value is less than the argument
        /// </summary>
        /// <param name="arg">Value to raise</param>
        /// <returns>The exponent of the values</returns>
        protected abstract bool? IsLessThan(MathValue arg);

        /// <summary>
        /// Determines if the value is greater than the argument
        /// </summary>
        /// <param name="arg">Value to raise</param>
        /// <returns>The exponent of the values</returns>
        protected abstract bool? IsGreaterThan(MathValue arg);

        #endregion

        #endregion

        #region Operators

        #region Mathematic Operators

        /// <summary>
        /// Serves in leu of a C# operator overload for power functions
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static MathValue PowerOperator(MathValue v1, MathValue v2)
        {
            //DetectVoids(v1, v2);

            return v1.Power(v2);
        }
        
        /// <summary>
        /// C# wrapper for add functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static MathValue operator +(MathValue v1, MathValue v2)
        {
            var native = v1.ConvertToNativeValue(v2);
            return v1.Add(native);
        }

        /// <summary>
        /// C# wrapper for subtract functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static MathValue operator -(MathValue v1, MathValue v2)
        {
            var native = v1.ConvertToNativeValue(v2);
            return v1.Subtract(native);
        }

        /// <summary>
        /// C# wrapper for multiply functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static MathValue operator *(MathValue v1, MathValue v2)
        {
            var native = v1.ConvertToNativeValue(v2);
            return v1.Multiply(native);
        }

        /// <summary>
        /// C# wrapper for divide functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static MathValue operator /(MathValue v1, MathValue v2)
        {
            var native = v1.ConvertToNativeValue(v2);
            return v1.Divide(native);
        }

        /// <summary>
        /// C# wrapper for modulus functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static MathValue operator %(MathValue v1, MathValue v2)
        {
            var native = v1.ConvertToNativeValue(v2);
            return v1.Modulo(native);
        }

        #endregion

        #region Logical Operators

        /// <summary>
        /// C# wrapper for and functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static MathValue operator &(MathValue v1,MathValue v2)
        {
            var native = v1.ConvertToNativeValue(v2);
            return v1.And(native);
        }

        /// <summary>
        /// C# wrapper for or functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static MathValue operator |(MathValue v1, MathValue v2)
        {
            var native = v1.ConvertToNativeValue(v2);
            return v1.Or(native);
        }

        /// <summary>
        /// C# wrapper for and functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static MathValue operator ^(MathValue v1, MathValue v2)
        {
            var native = v1.ConvertToNativeValue(v2);
            return v1.Xor(native);
        }

        

        #endregion

        #region Unary Operators

        /// <summary>
        /// C# wrapper for and functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <returns></returns>
        public static MathValue operator !(MathValue v1)
        {
            return v1.Runtime.NewBool(!v1.IsTrue());
        }

        /// <summary>
        /// C# wrapper for and functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <returns></returns>
        public static bool operator true(MathValue v1)
        {
            return v1.IsTrue();
        }

        /// <summary>
        /// C# wrapper for and functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <returns></returns>
        public static bool operator false(MathValue v1)
        {
            return !v1.IsTrue();
        }

        /// <summary>
        /// C# wrapper for and functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <returns></returns>
        public static MathValue operator -(MathValue v1)
        {
            return v1.ToMinus();
        }

        /// <summary>
        /// C# wrapper for and functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <returns></returns>
        public static MathValue operator +(MathValue v1)
        {
            return v1;
        }

        /// <summary>
        /// C# wrapper for and functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <returns></returns>
        public static MathValue operator ++(MathValue v1)
        {
            return v1.PlusPlus();
        }

        /// <summary>
        /// C# wrapper for and functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <returns></returns>
        public static MathValue operator --(MathValue v1)
        {
            return v1.MinusMinus();
        }

        /// <summary>
        /// C# wraper for the bit filp operator
        /// </summary>
        /// <param name="v1"></param>
        /// <returns></returns>
        public static MathValue operator ~(MathValue v1)
        {
            return v1.Flip();
        }

        #endregion

        #region Relational Operators

        /// <summary>
        /// C# wrapper for and functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static MathValue operator ==(MathValue v1, MathValue v2)
        {
            if (((object)v1 == null) && ((object)v2 == null))
                return null;

            if (ReferenceEquals(v1, v2))
                return v1.Runtime.True;

            if (((object)v1 == null) && ((object)v2 != null))
                return v2.Runtime.False;
            else if (((object)v1 != null) && ((object)v2 == null))
                return v1.Runtime.False;

            var result = v1.Equals(v2);
            return v1.Runtime.NewBool(result);
        }

        /// <summary>
        /// "Not Equals" function
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static MathValue operator !=(MathValue v1, MathValue v2)
        {
            var equals = (v1 == v2);
            return v1.Runtime.NewBool(!(bool)equals.Value);
        }

        /// <summary>
        /// C# wrapper for less than functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static MathValue operator <(MathValue v1,MathValue v2)
        {
            var native = v1.ConvertToNativeValue(v2);
            var result = v1.IsLessThan(native);
            if (result == null)
                return v1.Runtime.Undefined;
            return v1.Runtime.NewBool(result.Value);
        }

        /// <summary>
        /// C# wrapper for greater than functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static MathValue operator >(MathValue v1,MathValue v2)
        {
            var native = v1.ConvertToNativeValue(v2);
            var result = v1.IsGreaterThan(native);
            if (result == null)
                return v1.Runtime.Undefined;
            return v1.Runtime.NewBool(result.Value);
        }

        /// <summary>
        /// C# wrapper for and functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static MathValue operator >=(MathValue v1,MathValue v2)
        {
            var native = v1.ConvertToNativeValue(v2);
            var result = v1.IsLessThan(native);
            if (result == null)
                return v1.Runtime.Undefined;
            return v1.Runtime.NewBool(!result.Value);
        }

        /// <summary>
        /// C# wrapper for and functionality
        /// </summary>
        /// <param name="v1"></param>
        /// <param name="v2"></param>
        /// <returns></returns>
        public static MathValue operator <=(MathValue v1,MathValue v2)
        {
            var native = v1.ConvertToNativeValue(v2);
            var result = v1.IsGreaterThan(native);
            if (result == null)
                return v1.Runtime.Undefined;
            return v1.Runtime.NewBool(!result.Value);
        }

        #endregion

        #endregion


        #region IComparable<MathValue> Members

        public int CompareTo(MathValue other)
        {
            if (IsGreaterThan(other) == true)
                return 1;
            if (IsLessThan(other) == true)
                return -1;
            return 0;
        }

        #endregion
    }
}
